/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights
 * reserved. <a>http://www.cqcis.com</a>
 */
package com.cqcis.uip.base.framework.core.impl;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.ContextHandlerCollection;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHolder;
import org.mortbay.jetty.webapp.WebAppContext;

import com.cqcis.uip.base.framework.FrameworkConstants;
import com.cqcis.uip.base.framework.UIPServiceException;
import com.cqcis.uip.base.framework.core.ServletContainer;
import com.cqcis.uip.base.framework.utils.StringUtil;

/**
 * ServletContainer服务器
 * 
 * @author huangmy
 * @date   2009-3-26
 */
public class ServletContainerServer extends Server implements ServletContainer {
	private static String httpServiceViewContext = "/exports";
	private static Log log = LogFactory.getLog(ServletContainerServer.class);
    private Map<String, ContextMeta> contextURLs = new HashMap<String, ContextMeta>();
    private ContextHandlerCollection contexts; // contexts
    private boolean showServiceDetails;
    
    protected ServletContainerServer(int port, final boolean showServiceDetails) {
    	super(port);
    	ContextHandlerCollection contexts = new ContextHandlerCollection();
    	super.setHandler(contexts);
    	this.contexts = contexts;
    	this.showServiceDetails = showServiceDetails;
    	
    	// 注册默认
    	try {
			registerURL(httpServiceViewContext, "/*", new HttpServlet() {
				private static final long serialVersionUID = 1L;
				@Override
				protected void service(HttpServletRequest request,
						HttpServletResponse response) throws ServletException,
						IOException {
					if (showServiceDetails) {
						PrintWriter writer = response.getWriter();
						writer.write(getContextsDescription(null));
						writer.close();
					}					
				}
			}, null, true);
		} catch (Exception e) {
			throw new UIPServiceException(e);
		}
    }
    
    private String getDescriptionHead(String title, String content) {
    	return "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\"><title>"
    	+ title + "</title></head><body leftmargin=\"0\" topmargin=\"0\" marginheight=\"0\" marginwidth=\"0\" rightmargin=\"0\" bgcolor=\"#ffffff\" style=\"font:normal normal normal 10pt Courier New\"><div style=\"padding-bottom:10px\"><table border=\"0\" cellpadding=\"4\" cellspacing=\"0\" width=\"100%\"><tr><td style=\"background:#999999 none repeat scroll 0 0;\">"
    	 + content + "</tr><tr><td>";
    }
    
    private String getDescriptionBody() {
    	return "</tr></table></div></body></html>";
    }
    
    /**
     * context描述
     * 
     * @return
     */
    private String getContextsDescription(String contexthPath) {
    	contexthPath = StringUtil.trimWhiteToNull(contexthPath);
    	StringBuffer sb = new StringBuffer();
    	
    	// 列出所有的context
    	boolean allContexts = false;
    	Set<String> contexts = null;
    	if (contexthPath == null) {
    		contexts = this.contextURLs.keySet();
    		allContexts = true;
    	} else {
    		ContextMeta metas = this.contextURLs.get(contexthPath);
    		if (metas != null) {
    			contexts = metas.getUrls().keySet();
    		}
    	}
    	
    	if (contexts != null) {
    		if (allContexts) {
    			sb.append(getDescriptionHead(httpServiceViewContext, "&nbsp;&nbsp;ContextPaths exported in the server:"));
    		} else {
    			sb.append(getDescriptionHead(httpServiceViewContext, "&nbsp;&nbsp" + contexthPath));
    		}
    		sb.append("<ul>");
    		for (String context : contexts) { // CONTEXT
        		if (context.equals(httpServiceViewContext)) {
        			continue;
        		}
        		if (context.equals("/*")) {
        			context = "/";
        		}
        		String path = context.substring(context.indexOf("/") + 1);
        		sb.append("<li><a href=\"");
        		if (allContexts) {
        			sb.append("../");
        		}
        		sb.append(path).append("\">")
        		  .append(path).append("</a></li>");
        	}
    		sb.append("</ul>");
    	}
    	
    	sb.append(getDescriptionBody());
    	return sb.toString();
    }
    
    /**
	 * HTTP Invoker服务描述
	 * 
	 * @return
	 * @throws IOException 
	 */
	private String getHttpExporterServiceDescribeHTML(Class<?> serviceClazz) throws IOException {
		// 类描述文件
		StringBuffer sb = new StringBuffer();
		sb.append(getDescriptionHead(serviceClazz.getName(), ">>&nbsp;&nbsp;service details:"));
		sb.append("<div style=\"background:#E3E3E3\"");
		sb.append("<b>package </b>");
		
		sb.append(serviceClazz.getPackage().getName());
		sb.append(";<p><b>public interface </b>");
		sb.append(serviceClazz.getSimpleName());
		sb.append(" {<p>");
		
		Method[] methods = serviceClazz.getMethods();
		if (methods != null) {
			for (Method m : methods) {
				String methodStr = "";
				for (int i = 0; i < 4; i ++) {
					methodStr += "&nbsp;";
				}
				
				methodStr += m.getReturnType().getSimpleName();
				methodStr += "&nbsp;";
				methodStr += m.getName();
				methodStr += "(";
				Class<?>[] params = m.getParameterTypes();
				if (params != null) {
					for (int j = 0; j < params.length; j ++) {
						Class<?> param = params[j];
						methodStr += param.getSimpleName();
						methodStr += "&nbsp;";
						methodStr += "arg" + j;
						
						if (j < params.length - 1) {
							methodStr += ";&nbsp;";
						}
					}
				}
				methodStr += ");<p>";
				
				sb.append(methodStr);
			}
		}
				
		sb.append("}");
		sb.append("</div>");
		sb.append(getDescriptionBody());
		return sb.toString();		
	}
	
	private ServletHolder createDispatchHolder(final String contextPath, final Map<String, UrlServlet> mappings) {
		return new ServletHolder(new HttpServlet() {
			private static final long serialVersionUID = 1L;

			@Override
			protected void service(HttpServletRequest request,
					HttpServletResponse response) throws ServletException,
					IOException {
				String url = request.getRequestURI();
				    				
				url = url.substring(contextPath.length());
				url = StringUtil.trimWhiteToNull(url);
				if ("/".equals(url)) {
					url += "*";
				}
				UrlServlet servlet = mappings.get(url);
				if (servlet == null) {
					servlet = mappings.get("/*");
				}
				
				String method = request.getMethod(); 
				if (servlet != null) {    					
					try {
						Class<?> serviceInterface = servlet.serviceInterface;
						if ("GET".equalsIgnoreCase(method) && serviceInterface != null && showServiceDetails) {
							PrintWriter writer = response.getWriter();
							writer.write(getHttpExporterServiceDescribeHTML(serviceInterface));
							writer.close();
						} else {
							servlet.holder.service(request, response);
						}
					} catch (ServletException e) {
						log.error("Catch ServletException in handleRequest.", e);
						throw e;
					} catch (IOException e) {
						log.error("Catch IOException in handleRequest.", e);
						throw e;
					} catch (Throwable e) {
						log.error("Catch Exception in handleRequest, " + e.getClass().getName(), e);
						throw new ServletException(e);
					}
				} else {
					if ("GET".equalsIgnoreCase(method) && showServiceDetails) {
						// 显示contextPath下的详细信息
						PrintWriter writer = response.getWriter();
						writer.write(getContextsDescription(contextPath));
						writer.close();
					} else {
					    response.sendError(HttpServletResponse.SC_NOT_FOUND);
					}
				}
			}
		});
	}
    
    /**
     * 注册context
     * 
     * @param contextPath
     * @throws Exception 
     */
	private synchronized ContextMeta registerContext(String contextPathDefine, boolean self, 
			boolean webapp, String webWarPath) throws Exception {
    	final String contextPath = StringUtil.trimWhiteToNull(contextPathDefine);
    	
    	if (contextPath == null) {
    		throw new NullPointerException("the param can't be null");
    	}
    	
    	if (!contextPath.startsWith("/")) {
    		throw new IllegalArgumentException("the contextPath must be started with /");
    	}
    	
    	if (contextPath.endsWith("/")) {
    		throw new IllegalArgumentException("the contextPath can't be end with /");
    	}
    	
    	if (contextPath.equals(httpServiceViewContext) && !self) {
    		throw new IllegalArgumentException("the contextPath can't be named '" + httpServiceViewContext + "'");
    	}
    	
    	ContextMeta meta = this.contextURLs.get(contextPath);
    	if (meta == null) {
    		Map<String, UrlServlet> urlServlets = new HashMap<String, UrlServlet>();
    		Context context = null;
    		if (webapp) {
    			WebAppContext contextWeb = new WebAppContext(this.contexts, webWarPath, 
    					contextPath);
    			File tmpDir = new File(FrameworkConstants.WEBAPP_EXTRA_PATH + contextPath);
    			if (!tmpDir.exists()) {
    				tmpDir.mkdirs();
    			}
        		contextWeb.setTempDirectory(tmpDir);
        		
        		context = contextWeb;
    		} else {
    			context = new Context(this.contexts,contextPath,Context.SESSIONS);
    		}
    		
    		meta = new ContextMeta(context, urlServlets);
    		this.contextURLs.put(contextPath, meta);
    		final Map<String, UrlServlet> mappings = meta.getUrls();
    		ServletHolder holder = createDispatchHolder(contextPath, mappings);
        	meta.getContext().addServlet(holder, "/*");
    		context.start();
    	}
    	
    	return meta;
    }
            
    /**
     * 销毁context
     * 
     * @param contextPath
     * @throws Exception
     */
    private synchronized void destoryContext(String contextPath) throws Exception {
    	ContextMeta meta = getMeta(contextPath);
    	if (meta == null) {
    		return;
    	}
    	
    	this.contextURLs.remove(contextPath);
    	
    	Context context = meta.getContext();
    	if (context != null) {
    		this.contexts.removeHandler(context);
    		context.destroy();
    		context.stop();
    		
    		Handler[] handlers = this.contexts.getChildHandlers();    		
    		if (handlers == null || handlers.length <= 0) {
    			// stop server
    			super.stop();
    		}
    	}
    	
    	meta.removeAll();
    }
    
    /**
     * 注册URL
     * 
     * @param contextPath
     * @param url
     * @param servlet
     * @return
     * @throws Exception
     */
    public synchronized URLRegister registerURL(String contextPath, String url, HttpServlet servlet) throws Exception {
    	return registerURL(contextPath, url, servlet, null);
    }
    
    /**
     * 注册URL
     * 
     * @param contextPath
     * @param url
     * @param servlet
     * @param serviceInterface
     * @return
     * @throws Exception
     */
    public synchronized URLRegister registerURL(String contextPath, String url, 
    		         HttpServlet servlet, Class<?> serviceInterface) throws Exception {
    	return registerURL(contextPath, url, servlet, serviceInterface, false);
    }
    
    public synchronized URLRegister registerWAR(String contextPath, String war) throws Exception {
    	contextPath = StringUtil.trimWhiteToNull(contextPath);
    	
    	if (contextPath == null) {
    		throw new NullPointerException("the param can't be null");
    	}
    	
    	if (!contextPath.startsWith("/")) {
    		throw new IllegalArgumentException("the contextPath must be started with /");
    	}
    	
    	if (contextPath.endsWith("/")) {
    		throw new IllegalArgumentException("the contextPath can't be end with /");
    	}
    	
    	if (!StringUtil.hasText(war)) {
    		throw new NullPointerException("the WAR path can't be null.");
    	}
    	
    	ContextMeta meta = getMeta(contextPath);
    	if (meta != null) {
    		throw new UIPServiceException("the contextPath has exists with war: " + contextPath);
    	}
    	
    	// 注册
    	meta = registerContext(contextPath, false, true, war);
    	
    	return new URLRegister(contextPath, null, this);
    }
    
    /**
     * 注册URL
     * 
     * @param contextPath
     * @param url
     * @param servlet
     * @param serviceInterface
     * @return
     * @throws Exception
     */
    private synchronized URLRegister registerURL(String contextPath, String url, 
    		         HttpServlet servlet, Class<?> serviceInterface, boolean self) throws Exception {
    	contextPath = StringUtil.trimWhiteToNull(contextPath);
    	url = StringUtil.trimWhiteToNull(url);
    	
    	if (contextPath == null || url == null || servlet == null) {
    		throw new NullPointerException("the param can't be null");
    	}
    	
    	if (!contextPath.startsWith("/")) {
    		throw new IllegalArgumentException("the contextPath must be started with /");
    	}
    	
    	if (contextPath.endsWith("/")) {
    		throw new IllegalArgumentException("the contextPath can't be end with /");
    	}
    	
    	if (!url.startsWith("/")) {
    		throw new IllegalArgumentException("the url must be started with /");
    	}
    	
    	if (url.endsWith("/")) {
    		throw new IllegalArgumentException("the url can't be end with /");
    	}
    	
    	ContextMeta meta = getMeta(contextPath);
    	if (meta == null) {
    		meta = registerContext(contextPath, self, false, null);
    	}
    	    	
    	meta.addURL(new UrlServlet(url, servlet, serviceInterface));
    	
    	return new URLRegister(contextPath, url, this);
    }
    
    public static class URLRegister {
    	private String contextPath;
    	private String url;
    	private ServletContainerServer server;
    	
    	URLRegister(String contextPath, String url, ServletContainerServer server) {
    		this.contextPath = contextPath;
    		this.url = url;
    		this.server = server;
    	}
    	
    	/**
    	 * 关闭
    	 * 
    	 * @throws Exception
    	 */
		public void close() throws Exception {
			if (this.server != null && this.contextPath != null) {
				this.server.destoryURL(this.contextPath, this.url);
			}			
		}
    }
    
    /**
     * 销毁URL, 及其对应的Servlet
     * 
     * @param contextPath
     * @param url
     * @throws Exception 
     */
    private synchronized void destoryURL(String contextPath, String url) throws Exception {
    	ContextMeta meta = getMeta(contextPath);
    	if (meta == null) {
    		return;
    	}
    	
    	if (url != null) {
    	    meta.removeURL(url);
    	}
    	if (meta.getUrls().size() <= 0) {
    		// destory context
    		destoryContext(contextPath);
    	}
    }
    
    /**
     * 获取元数据描述信息
     * 
     * @param contextPath
     * @return
     */
    private ContextMeta getMeta(String contextPath) {
    	ContextMeta meta = this.contextURLs.get(contextPath);
    	return meta;
    }
    
    private class UrlServlet {
    	private String url;
    	private HttpServlet holder;
    	private Class<?> serviceInterface;
    	
    	public UrlServlet(String url, HttpServlet holder, Class<?> serviceInterface) {
    		this.url = url;
    		this.holder = holder;
    		this.serviceInterface = serviceInterface;
    	}
    }
    
    private class ContextMeta {
    	private Map<String, UrlServlet> urlServlets; // URL
    	private Context context;  // CONTEXT
    	
    	public ContextMeta(Context context, Map<String, UrlServlet> urlServlets) {
    		if (context == null || urlServlets == null) {
    			throw new NullPointerException("the param can't be null.");
    		}
    		
    		this.context = context;
    		this.urlServlets = urlServlets;
    	}
    	
		public Map<String, UrlServlet> getUrls() {
			return urlServlets;
		}
		
		public Context getContext() {
			return context;
		}
		
		public void addURL(UrlServlet urlServlet) throws Exception {
			UrlServlet old = this.urlServlets.get(urlServlet.url);
			if (old != null && old.holder != urlServlet.holder) {
				old.holder.destroy();
			}
			
			urlServlet.holder.init();
			this.urlServlets.put(urlServlet.url, urlServlet);
		}
		
		public void removeURL(String url) throws Exception {
			UrlServlet old = this.urlServlets.remove(url);
			if (old != null) {
				old.holder.destroy();
			}
		}
		
		public void removeAll() throws Exception {
			for (Entry<String, UrlServlet> entry : this.urlServlets.entrySet()) {
				UrlServlet old = entry.getValue();
				if (old != null) {
					old.holder.destroy();
				}
			}
			
			this.urlServlets.clear();
		}
    }
}
